/**
 * 
 */
package org.statewalker.graph.util;

import org.statewalker.graph.AbstractNodeWalker;
import org.statewalker.graph.INodeWalkerListener;
import org.statewalker.graph.INodeWalkerSource;
import org.statewalker.graph.NodeWalker;
import org.statewalker.graph.NodeWalkerListener;
import org.statewalker.graph.AbstractNodeWalker.Mode;

/**
 * This class is used to iterate over graph structures. It returns all graph
 * nodes one-by-one until the whole structure is traversed. The
 * {@link #shiftItem()} is the main method of this class. For each call it
 * returns a new graph node. When the iteration is finished it returns
 * <code>null</code>.
 * 
 * @author kotelnikov
 */
public class NodeShifter<T, E extends Exception> {

    /**
     * The listener which is used to notify about individual steps of the
     * walking process.
     */
    private INodeWalkerListener<T, E> fListener;

    /**
     * A node walker containing the current stack of nodes
     */
    private AbstractNodeWalker<T, E> fWalker;

    /**
     * THe main constructor creating an internal node walker and initializing
     * object fields
     * 
     * @param source the source of sub-nodes in the graph
     * @param listener the listener used to notify about individual steps of the
     *        graph traversal process
     * @param topNode the topmost node of the graph; starting from this node the
     *        graph iteration is started Exception,
     */
    public NodeShifter(
        INodeWalkerSource<T, E> source,
        INodeWalkerListener<T, E> listener,
        T topNode) {
        fWalker = newNodeWalker(source, topNode);
        fListener = listener != null
            ? listener
            : new NodeWalkerListener<T, E>();
    }

    /**
     * This constructor initializes the object with a default (empty/do-nothing)
     * listener.
     * 
     * @param source the source of sub-nodes for all elements in the graph
     * @param topNode the topmost node of the graph from which the iteration
     *        process should be started
     */
    public NodeShifter(INodeWalkerSource<T, E> source, T topNode) {
        this(source, null, topNode);
    }

    /**
     * Defines the traversal mode for this iterator. The mode defines when the
     * control it returned to the iterator caller - before entering in a node,
     * after exiting from a node, for leaf nodes only, or for each step
     * (entering and exiting from a node). By default this method returns
     * {@link Mode#IN} value, which means that the control is returned after
     * entering in graph nodes. This method can be overloaded in subclasses.
     * 
     * @param mode the graph traversal mode
     */
    protected boolean checkStatus(Mode status) {
        return status.check(Mode.IN);
    }

    /**
     * Returns the status of the walker
     * 
     * @return the status of the walker
     */
    public Mode getStatus() {
        return fWalker.getStatus();
    }

    /**
     * Returns the internal node walker
     * 
     * @return the internal node walker
     */
    public AbstractNodeWalker<T, E> getWalker() {
        return fWalker;
    }

    /**
     * Creates and returns a new node walker used by this iterator to keep the
     * state of the iteration process - it keeps current position in the graph
     * and the currently active node. This method by default creates an instance
     * of a simple {@link java.util.Stack stack}-based implementation of the
     * NodeWalker. This method can be overloaded in subclasses to create an
     * another implementation of a node walker.
     * 
     * @param source the source of sub-nodes for all elements in the graph
     * @param topNode the topmost node of the graph from which the iteration
     *        process should be started
     * @return a new node walker defining the current position of the iterator
     *         in the graph structure
     */
    protected AbstractNodeWalker<T, E> newNodeWalker(
        INodeWalkerSource<T, E> source,
        T topNode) {
        return new NodeWalker<T, E>(source, topNode);
    }

    /**
     * Returns the next node in the traversed tree structure or
     * <code>null</code> if there is nothing to return.
     * 
     * @return the next node in the tree or <code>null</code> if there is no
     *         more nodes to return.
     * @throws E
     */
    public T shiftItem() throws E {
        Mode status = fWalker.getStatus();
        while (status != Mode.TERMINTATED) {
            status = fWalker.shift(fListener);
            if (checkStatus(status))
                break;
        }
        // return status != Mode.TERMINTATED ? fWalker.getCurrentNode() : null;
        return fWalker.getCurrentNode();
    }
}
